<template>
  <div class="df-input-wrapper">
    <div
      ref="field"
      :is="componentType"
      :disabled="disabled"
      :form-submitting="formSubmitting"
      :field="field"
      :data-source="dataSource"
      :value="value"
      :style="[field.inputStyle]"
      :class="[field.inputClass]"
      :schema="schema"
      @submit="submit"
      @input="onChange"></div>
    <i v-if="pending" class="fa fa-spinner fa-pulse fa-fw df-input-pending"></i>
    <div v-if="isLoading" class="df-input-loading-mask">
      <i class="fa fa-spinner fa-pulse fa-fw"></i>
    </div>
  </div>
</template>

<script>
  import Vue from 'vue'
  import _ from 'lodash'
  import validateRules from '../validations'
  import InputTypes from '../input-types'

  function validateNextRule (vm, val, curIdx, validators, model, formModel, field, formSchema) {
    if (validators.length <= curIdx) {
      vm.setError({
        field: vm.field,
        model: vm.model
      })
//      Vue.set(vm.model, field.name, val)
      return
    }
    let validator = validators[curIdx]
    let rst = validator.rule.validate(val, validator.config, model, formModel, field, formSchema)
    if (rst.valid.result.then && rst.valid.result.then instanceof Function) {
      vm.setValidatePending(true)
      rst.valid.result.then(valid => {
        if (!valid) {
          vm.setError({
            field: vm.field,
            model: vm.model,
            rule: validator.rule.rule,
            errorTemplate: rst.valid.errorMessage || rst.errorTemplate,
            errorParams: rst.valid.errorMessage ? null : rst.errorParams
          })
        } else {
          curIdx++
          validateNextRule(vm, val, curIdx, validators, model, formModel, field, formSchema)
        }
      })['finally'](() => {
        vm.setValidatePending(false)
      })
    } else {
      if (rst.valid.result) {
        curIdx++
        validateNextRule(vm, val, curIdx, validators, model, formModel, field, formSchema)
      } else {
        vm.setError({
          field: vm.field,
          model: vm.model,
          rule: validator.rule.rule,
          errorTemplate: rst.valid.errorMessage || rst.errorTemplate,
          errorParams: rst.valid.errorMessage ? null : rst.errorParams
        })
      }
    }
  }

  export default {
    props: {
      index: Number,
      schema: {
        type: Object,
        default () {
          return {}
        }
      },
      field: {
        type: Object,
        default () {
          return {}
        }
      },
      value: {},
      model: {
        type: Object,
        default () {
          return {}
        }
      },
      formGroup: {},
      formModel: {
        type: Object,
        default () {
          return {}
        }
      },
      formSubmitting: {
        type: Boolean,
        default: false
      }
    },
    mounted () {
      let form = this.form

      if (_.isNil(form.$fields)) {
        form.$fields = {}
      }

      this.key = `${this.model.__key}_${this.field.name}`

      form.$fields[this.key] = {
        vm: this,
        model: this.model,
        field: this.field
      }

      let context = form.$fields[this.key]

      if (this.model[this.field.name] === undefined) {
        Vue.set(this.model, this.field.name, null)
      }

      if (_.isFunction(this.field.dataSource)) {
        context.reload = _.debounce(() => {
          this.loadTimes++
          let curTime = this.loadTimes
          let rst = this.field.dataSource(this.model, this.form.formModel, this.field)
          if (rst && rst.then && rst.then instanceof Function) {
            this.setLoading(true)
            rst.then(data => {
              this.dataSource = data
            })['finally'](() => {
              if (this.loadTimes === curTime) {
                this.setLoading(false)
              }
            })
          }
        }, 200)
        context.reload()

        // dataSource dependence
        if (this.field.dataSourceDependsOn) {
          if (!form.$dataSourceDependsRelation) {
            form.$dataSourceDependsRelation = {}
          }
          _.chain(this.field.dataSourceDependsOn.split(','))
            .map(item => {
              return item.trim()
            })
            .filter(item => {
              return !!item
            })
            .forEach(item => {
              let key = `${this.model.__key}_${item}`
              if (form.$dataSourceDependsRelation[key]) {
                form.$dataSourceDependsRelation[key].push(this.field.name)
              } else {
                form.$dataSourceDependsRelation[key] = [this.field.name]
              }
            }).value()
        }
      }

      this.debounceValidateNextRule = _.debounce(validateNextRule, this.field.delay || 200)

      let rules = []
      if (this.field.validators) {
        _.forEach(this.field.validators, (value, key) => {
          if (key === 'dependsOn') {
            if (!form.$validationDependsRelation) {
              form.$validationDependsRelation = {}
            }
            _.chain(value.split(','))
              .map(item => {
                return item.trim()
              }).filter(item => {
                return !!item
              })
              .forEach(item => {
                let key = `${this.model.__key}_${item}`
                if (form.$validationDependsRelation[key]) {
                  form.$validationDependsRelation[key].push(this.field.name)
                } else {
                  form.$validationDependsRelation[key] = [this.field.name]
                }
              }).value()
            return
          }
          let rule = validateRules[key]
          if (!rule) {
            throw new Error(`rule named ${key} not exist`)
          }
          rules.push({
            rule: rule,
            config: this.field.validators
          })
        })
        rules = _.orderBy(rules, [item => {
          return item.rule.priority
        }])
      }
      this.validators = rules

      this.validateSync()
    },
    data () {
      return {
        isLoading: false,
        debounceValidateNextRule: Function,
        loadTimes: 0,
        dataSource: [],
        validators: [],
        pending: false
      }
    },
    destroyed () {
      this.$emit('field-destroyed', {field: this.field, model: this.model})
      delete this.form.$fields[this.key]
    },
    computed: {
      tabIndex () {
        return 0
      },
      componentType () {
        return typeof this.field.componentType === 'string' ? InputTypes[this.field.componentType] : this.field.componentType
      },
      form () {
        let parent = this.$parent
        while (parent && _.indexOf(parent.$el.classList, 'df-form') === -1) {
          parent = parent.$parent
        }
        return parent
      },
      disabled () {
        if (this.formSubmitting) {
          return true
        }
        if (_.isNil(this.field.disabled)) {
          return false
        }
        if (_.isFunction(this.field.disabled)) {
          return this.field.disabled(this.model, this.formModel, this.field)
        }
        return !!this.field.disabled
      }
    },
    methods: {
      submit () {
        this.form.$el.dispatchEvent(new Event('submit'))
      },
      setLoading (isLoading) {
        this.isLoading = isLoading
        this.$emit('loading', {field: this.field, model: this.model, isLoading})
      },
      setError (params) {
        this.$emit('validate', params)
      },
      setValidatePending (isPending) {
        this.pending = isPending
        this.$emit('pending', {field: this.field, model: this.model, isPending})
      },
      onChange (val) {
        if (val === this.model[this.field.name]) {
          return
        }
        Vue.set(this.model, this.field.name, val)
        this.validateSync(val)
        let form = this.form
        if (form.$validationDependsRelation && form.$validationDependsRelation[this.key]) {
          _.forEach(form.$validationDependsRelation[this.key], item => {
            let field = form.$fields[`${this.model.__key}_${item}`]
            if (field) {
              field.vm.validateSync()
            }
          })
        }
        if (form.$dataSourceDependsRelation && form.$dataSourceDependsRelation[this.key]) {
          _.forEach(form.$dataSourceDependsRelation[this.key], item => {
            let field = form.$fields[`${this.model.__key}_${item}`]
            if (field) {
              field.vm.dataSource = []
              field.reload && field.reload()
            }
          })
        }
      },
      validateSync (val) {
        if (val === undefined) {
          val = this.model[this.field.name]
        }
        this.debounceValidateNextRule(this, val, 0, this.validators, this.model, this.form.formModel, this.field, this.form.formSchema)
      }
    },
    watch: {
      value (newVal, oldVal) {
        if (newVal !== oldVal) this.validateSync(newVal)
      }
    }
  }
</script>
